#include <linux/init.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/irq.h>
#include <stdbool.h>

struct private_data {
    int irq;
    resource_size_t io;
    void __iomem *ioaddr;
    u64 range;
    unsigned int flags;
    const char *name;
    bool ismem;
};

static void rk3588_get_config(struct pci_dev *dev)
{
    u8 val1;
    u16 val2;
    u32 val3;

    pci_read_config_word(dev, PCI_VENDOR_ID, &val2);
    dev_info(&dev->dev, "vendorID:%x\n", val2);
    pci_read_config_word(dev, PCI_DEVICE_ID, &val2);
    dev_info(&dev->dev, "deviceID:%x\n", val2);
    pci_read_config_byte(dev, PCI_REVISION_ID, &val1);
    dev_info(&dev->dev, "revisionID:%x\n", val1);
    pci_read_config_dword(dev, PCI_CLASS_REVISION, &val3);
    dev_info(&dev->dev, "class:%x\n", val3);
}

static irqreturn_t rk3588_pcie_irq(int irq, void *dev)
{
    struct pci_dev *pcie_device = dev;
    struct private_data *pcie = pci_get_drvdata(pcie_device);
    if (!pcie || strcmp(pcie->name, "pci_realtek_wifi")) {
        dev_err(&pcie_device->dev, "irq %d isn't this one!\n", irq);
        return IRQ_NONE;
    }
    dev_info(&pcie_device->dev, "interrupt\n");
    return IRQ_HANDLED;
}

static int rk3588_pcie_probe(struct pci_dev *dev, const struct pci_device_id *id)
{
    int ret;
    struct private_data *pcie;

    ret = pci_enable_device(dev);
    if (ret) {
        dev_err(&dev->dev, "pci_enable_device failed.ret=%d\n", ret);
        goto PCI_ENABLE_DEV_FAILED;
    }

    pcie = kzalloc(sizeof(struct private_data), GFP_KERNEL);
    if (!pcie) {
        dev_err(&dev->dev, "allocate private_data failed\n");
        goto ALLOCATE_PRIV_DATA_FAILED;
    }

    pcie->name = "pci_realtek_wifi";
    pcie->irq = dev->irq;
    if (pcie->irq < 0) {
        dev_err(&dev->dev, "pcie irq is disable\n");
        goto IRQ_FAILED;
    }

    pcie->io = pci_resource_start(dev, 0);
    pcie->range = (u64)pci_resource_end(dev, 0) - (u64)pcie->io + 1;
    pcie->flags = pci_resource_flags(dev, 0);
    dev_info(&dev->dev, "pcie: start:[%llx] range:[%llx] flags:[%x]\n",
        (u64)pcie->io, pcie->range, pcie->flags);
    dev_info(&dev->dev, "pcie base addr is io%s\n", (pcie->flags & IORESOURCE_MEM) ? "mem" : "port");
    pcie->ismem = pcie->flags & IORESOURCE_MEM;

    /* 在Linux内核中，request_mem_region()函数用于将一个特定的物理内存区域标记为已经被使用。
       这个函数通常在驱动程序初始化时被调用，以确保驱动程序可以安全地访问硬件设备所使用的内存区域。
       这个函数等效于:request_mem_region(pcie->io, pcie->range, pcie->name) 
       应该使用pci_request_regions函数，该函数会同时申请IO和MEM内存区域
    */
    ret = pci_request_regions(dev, pcie->name);
    if (ret) {
        dev_err(&dev->dev, "pci request memory failed.ret=%d\n", ret);
        goto REQUEST_MEM_FAILED;
    }

    if (pcie->ismem) {
        /* 这个函数等效于:ioremap(pcie->io, pcie->range) 
           pci设备有两种访问方式：内存访问和IO访问，只有内存访问才可以用这个函数做映射 
        */
        pcie->ioaddr = pci_ioremap_bar(dev, 0);
    } else {
        pcie->ioaddr = ioremap(pcie->io, pcie->range);
    }
    if (!pcie->ioaddr) {
        dev_err(&dev->dev, "pci ioremap failed\n");
        goto IOREMAP_FAILED;
    } 
    
    pci_set_drvdata(dev, pcie);
    /* irq标志默认为 IRQF_SHARED */
    ret = request_threaded_irq(pcie->irq, rk3588_pcie_irq, NULL, IRQF_SHARED, pcie->name, dev);
    if (ret) {
        dev_err(&dev->dev, "request irq failed.ret=%d\n", ret);
        goto REQUEST_IRQ_FAILED;
    }

    rk3588_get_config(dev);
    return 0;
REQUEST_IRQ_FAILED:
    iounmap(pcie->ioaddr);    
IOREMAP_FAILED:
    pci_release_mem_regions(dev);
REQUEST_MEM_FAILED:
IRQ_FAILED:
    kfree(pcie);
ALLOCATE_PRIV_DATA_FAILED:  
    pci_disable_device(dev);
PCI_ENABLE_DEV_FAILED:
    return ret;
}

static void rk3588_pcie_remove(struct pci_dev *dev)
{
    struct private_data *pcie = pci_get_drvdata(dev);
    if (!pcie) {
        return;
    }
    free_irq(pcie->irq, dev);
    iounmap(pcie->ioaddr);    
    pci_release_mem_regions(dev);
    kfree(pcie);
    pci_disable_device(dev);
}

static const struct pci_device_id g_pcie_id_table[] = {
    {PCI_DEVICE(PCI_VENDOR_ID_REALTEK, PCI_ANY_ID)},
    {}
};

static struct pci_driver g_pcie_driver = {
    .name = "pci_realtek_wifi",
    .probe = rk3588_pcie_probe,
    .remove = rk3588_pcie_remove,
    .id_table = g_pcie_id_table,
};

static int __init rk3588_pcie_init(void)
{
    int ret;
    ret = pci_register_driver(&g_pcie_driver);
    if (ret) {
        printk(KERN_ERR "[%s][%d]register pcie driver failed.ret=%d\n",
            __func__, __LINE__, ret);
        return ret;
    }
    return 0;
}

static void __exit rk3588_pcie_exit(void)
{
    pci_unregister_driver(&g_pcie_driver);
}

module_init(rk3588_pcie_init);
module_exit(rk3588_pcie_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Yujie Guo");
MODULE_DESCRIPTION("rk3588-pcie driver for Learning");
